/**
 * Copyright (c) 2010 Simple Graph Tools contributors. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id$
 */
package org.sgt.coredef.util;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.sgt.coredef.Chain;
import org.sgt.coredef.Condition;
import org.sgt.coredef.Constraint;
import org.sgt.coredef.CoredefFactory;
import org.sgt.coredef.CoredefPackage;
import org.sgt.coredef.DecomposableNode;
import org.sgt.coredef.DescribedObject;
import org.sgt.coredef.Edge;
import org.sgt.coredef.Node;
import org.sgt.coredef.ProxyEdge;
import org.sgt.coredef.ProxyNode;

/**
 * @author BUTTIGHOFFER-A
 * 
 */
public final class NodeHelper {
	
	/**
	 * Helper Class has a private constructor.
	 */
	private NodeHelper() {
		super();
	}

	/**
	 * Determines whether two {@link Node}s are comparable :
	 * <UL>
	 * <LI>They are the same ( == ) object</LI>
	 * <LI>or are {@link ProxyNode} pointing to
	 * the same ( == ) representation.</LI>
	 * <LI>or are two {@link DecomposableNode} pointing to comparable
	 * {@link Chain}</LI>
	 * </UL>
	 * In the case of {@link ProxyNode} only the representedObject is checked
	 * (constraints and condition on two proxies or a proxy and a node are
	 * ignored) .
	 * 
	 * @param node1
	 *            the first {@link Node}
	 * @param node2
	 *            the second {@link Node}
	 * @return true only when objects are comparable.
	 */
	public static boolean areComparable(final Node node1, final Node node2) {

		if (node1 == node2) {
			return true;
		}
		if (node1 instanceof ProxyNode) {
			ProxyNode prox = (ProxyNode) node1;
			if (node2 instanceof ProxyNode) {
				ProxyNode prox2 = (ProxyNode) node2;
				return prox.getRepresentedObject() == prox2
						.getRepresentedObject();

			} else {
				return prox.getRepresentedObject() == node2;
			}
		} else if (node1 instanceof DecomposableNode) {
			DecomposableNode decomp = (DecomposableNode) node1;
			if (node2 instanceof ProxyNode) {
				ProxyNode prox2 = (ProxyNode) node2;
				return decomp == prox2.getRepresentedObject();
			} else {
				if (node2 instanceof DecomposableNode) {
					DecomposableNode decomp2 = (DecomposableNode) node2;
					EList<Chain> chains1 = decomp.getRepresentedChains();
					EList<Chain> chains2 = decomp2.getRepresentedChains();
					if (chains1.size() != chains2.size()) {
						return false;
					}
					Node start1 = null;
					Node start2 = null;
					Node end1 = null;
					Node end2 = null;
					if (chains1.size() > 0) {
						start1 = chains1.iterator().next().getStartNode();
						start2 = chains2.iterator().next().getStartNode();
						end1 = chains1.iterator().next().getEndNode();
						end2 = chains2.iterator().next().getEndNode();
					}
					if (areComparable(start1, start2)
							&& areComparable(end1, end2)) {
						for (Chain chain : chains1) {
							boolean found = false;
							for (Chain second : chains2) {
								if (ChainHelper.areComparable(chain, second)) {
									found = true;
									break;
								}
							}
							if (!found) {
								return false;
							}
						}
						return true;
					} else {
						return false;
					}
				}
				return decomp == node2;
			}
		} else {
			if (node2 instanceof ProxyNode) {
				ProxyNode prox2 = (ProxyNode) node2;
				return node1 == prox2.getRepresentedObject();
			}
		}
		return false;
	}

	/**
	 * Creates a {@link ProxyNode} on given {@link Node}. If the node is a
	 * {@link DescribedObject}, the name and summary of the proxy will be
	 * synchronized via the {@link Proxy#syncProxy(EList)} method. Beware that
	 * when the node is a ProxyNode, it's representation and not the node itself
	 * will be used as representation of the returned proxy.
	 * 
	 * @param node
	 *            to create proxy from.
	 * @return a new proxy on given node.
	 */
	public static ProxyNode createProxyNode(final Node node) {
		ProxyNode proxy = CoredefFactory.eINSTANCE.createProxyNode();
		Node representation = node;
		if (node instanceof ProxyNode) {
			representation = ((ProxyNode) node).getRepresentedObject();
		}
		proxy.setRepresentedObject(representation);
		if (representation instanceof DescribedObject) {
			// Making sure we have a name and summary attributes...
			EList<EStructuralFeature> featuresList 
				= new BasicEList<EStructuralFeature>();
			featuresList
					.add(CoredefPackage.eINSTANCE.getDescribedObject_Name());
			featuresList.add(CoredefPackage.eINSTANCE
					.getDescribedObject_Summary());
			proxy.syncProxy(featuresList);
		}
		return proxy;
	}

	/**
	 * Returns a {@link Set} of all preconditions of a {@link Node}. If the node
	 * is a {@link ProxyNode}, all node and represented object's preconditions
	 * are returned.
	 * 
	 * @param node
	 *            to process.
	 * @return a set of conditions. may be empty but not null.
	 */
	public static Set<Condition> getAllPreConditions(final Node node) {
		Set<Condition> conds = new HashSet<Condition>();
		if (node != null) {
			for (Condition cond : node.getPreConditions()) {
				if (!conds.contains(cond)) {
					conds.add(cond);
				}
			}
			if (node instanceof ProxyNode) {
				Node pointed = (ProxyNode) node;
				if (pointed != null) {
					for (Condition cond : pointed.getPreConditions()) {
						if (!conds.contains(cond)) {
							conds.add(cond);
						}
					}
				}
			}
		}
		return conds;
	}

	/**
	 * Returns a {@link Set} of all postconditions of a {@link Node}. If the
	 * node is a {@link ProxyNode}, all node and represented object's
	 * postconditions are returned.
	 * 
	 * @param node
	 *            to process.
	 * @return a set of conditions. may be empty but not null.
	 */
	public static Set<Condition> getAllPostConditions(final Node node) {
		Set<Condition> conds = new HashSet<Condition>();
		if (node != null) {
			for (Condition cond : node.getPostConditions()) {
				if (!conds.contains(cond)) {
					conds.add(cond);
				}
			}
			if (node instanceof ProxyNode) {
				Node pointed = (ProxyNode) node;
				if (pointed != null) {
					for (Condition cond : pointed.getPostConditions()) {
						if (!conds.contains(cond)) {
							conds.add(cond);
						}
					}
				}
			}
		}
		return conds;
	}

	/**
	 * Returns a {@link Set} of all {@link Constraint}s of a {@link Node}. If
	 * the node is a {@link ProxyNode}, all node and represented object's
	 * constraints are returned.
	 * 
	 * @param node
	 *            to process.
	 * @return a set of constraints. may be empty but not null.
	 */
	public static Set<Constraint> getAllConstraints(final Node node) {
		Set<Constraint> conds = new HashSet<Constraint>();
		if (node != null) {
			for (Constraint cond : node.getConstraints()) {
				if (!conds.contains(cond)) {
					conds.add(cond);
				}
			}
			if (node instanceof ProxyNode) {
				Node pointed = (ProxyNode) node;
				if (pointed != null) {
					for (Constraint cond : pointed.getConstraints()) {
						if (!conds.contains(cond)) {
							conds.add(cond);
						}
					}
				}
			}
		}
		return conds;
	}

	/**
	 * Returns a {@link Set} of all preconditions of a {@link Edge}. If the node
	 * is a {@link ProxyNode}, all node and represented object's preconditions
	 * are returned.
	 * 
	 * @param edge
	 *            to process.
	 * @return a set of conditions. may be empty but not null.
	 */
	public static Set<Condition> getAllPreConditions(final Edge edge) {
		Set<Condition> conds = new HashSet<Condition>();
		if (edge != null) {
			for (Condition cond : edge.getPreConditions()) {
				if (!conds.contains(cond)) {
					conds.add(cond);
				}
			}
			if (edge instanceof ProxyEdge) {
				Edge pointed = (ProxyEdge) edge;
				if (pointed != null) {
					for (Condition cond : pointed.getPreConditions()) {
						if (!conds.contains(cond)) {
							conds.add(cond);
						}
					}
				}
			}
		}
		return conds;
	}

	/**
	 * Returns a {@link Set} of all postconditions of a {@link Edge}. If the
	 * node is a {@link ProxyEdge}, all node and represented object's
	 * postconditions are returned.
	 * 
	 * @param edge
	 *            to process.
	 * @return a set of conditions. may be empty but not null.
	 */
	public static Set<Condition> getAllPostConditions(final Edge edge) {
		Set<Condition> conds = new HashSet<Condition>();
		if (edge != null) {
			for (Condition cond : edge.getPostConditions()) {
				if (!conds.contains(cond)) {
					conds.add(cond);
				}
			}
			if (edge instanceof ProxyEdge) {
				Edge pointed = (ProxyEdge) edge;
				if (pointed != null) {
					for (Condition cond : pointed.getPostConditions()) {
						if (!conds.contains(cond)) {
							conds.add(cond);
						}
					}
				}
			}
		}
		return conds;
	}

	/**
	 * Returns a {@link Set} of all {@link Constraint}s of a {@link Edge}. If
	 * the node is a {@link ProxyEdge}, all node and represented object's
	 * constraints are returned.
	 * 
	 * @param edge
	 *            to process.
	 * @return a set of constraints. may be empty but not null.
	 */
	public static Set<Constraint> getAllConstraints(final Edge edge) {
		Set<Constraint> conds = new HashSet<Constraint>();
		if (edge != null) {
			for (Constraint cond : edge.getConstraints()) {
				if (!conds.contains(cond)) {
					conds.add(cond);
				}
			}
			if (edge instanceof ProxyEdge) {
				Edge pointed = (ProxyEdge) edge;
				if (pointed != null) {
					for (Constraint cond : pointed.getConstraints()) {
						if (!conds.contains(cond)) {
							conds.add(cond);
						}
					}
				}
			}
		}
		return conds;
	}

	/**
	 * Filters a {@link Set} of ordered nodes list keeping only unique series of
	 * {@link Node}s : if two lists of nodes which are comparable (as defined in
	 * {{@link #areComparable(EList, EList)}, only one will be included in the
	 * returned {@link Set}.
	 * 
	 * @param sequence
	 *            the sequence to filter.
	 * @return the filtered sequence (a new sequence : the original is intact).
	 */
	public static Set<EList<Node>> filter(final Set<EList<Node>> sequence) {
		if (sequence == null) {
			return null;
		}
		Set<EList<Node>> filtered = new HashSet<EList<Node>>();
		for (EList<Node> nodes : sequence) {
			int found = 0;
			for (EList<Node> fNodes : filtered) {
				if (NodeHelper.areComparable(nodes, fNodes)) {
					found = found + 1;
				}
			}
			if (filtered.isEmpty() || found < 1) {
				filtered.add(nodes);
			}
		}
		return filtered;
	}

	/**
	 * Determines whether two ordered lists of Nodes are comparable.
	 * 
	 * @param nodes
	 *            {@link Node}.
	 * @param fNodes
	 *            {@link Node}.
	 * @return true only if nodes ordered lists are comparable.
	 */
	public static boolean areComparable(final EList<Node> nodes,
			final EList<Node> fNodes) {
		if (nodes == fNodes) {
			return true;
		}
		if (nodes != null) {
			if (nodes.size() != fNodes.size()) {
				return false;
			}
			for (int i = 0; i < nodes.size(); i++) {
				Node node = nodes.get(i);
				Node fNode = fNodes.get(i);
				if (node == fNode) {
					continue;
				} else {
					if (!NodeHelper.areComparable(node, fNode)) {
						return false;
					}
					if (!node.getConstraints().containsAll(
							fNode.getConstraints())
							|| !fNode.getConstraints().containsAll(
									node.getConstraints())) {
						return false;
					}
					if (!node.getPreConditions().containsAll(
							fNode.getPreConditions())
							|| !fNode.getPreConditions().containsAll(
									node.getPreConditions())) {
						return false;
					}
					if (!node.getPostConditions().containsAll(
							fNode.getPostConditions())
							|| !fNode.getPostConditions().containsAll(
									node.getPostConditions())) {
						return false;
					}
				}
			}
			return true;
		}
		return false;
	}

}
